/****************************************************************************
 * Copyright (C) 2009-2011 Dimok
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************/
#include "Application.h"
#include "../System/sys.h"
#include "../System/video.h"
#include "../System/input.h"
#include "../Themes/CTheme.h"
#include "../Menu/MainMenu.h"
#include "../Prompts/HomeMenu.h"
#include "../Prompts/SearchWindow.h"
#include "../Prompts/ProgressWindow.h"
#include "../DeviceControls/DevicesSwap.h"
#include "../SoundOperations/MusicPlayer.h"

Application *Application::instance = NULL;
bool Application::exitApplication = false;
bool Application::bReset = false;
bool Application::bShutdown = false;
bool Application::needReload = false;
bool Application::reloading = false;
short Application::reloadType = RELOAD_NONE;

Application::Application()
	: GuiFrame(screenwidth, screenheight) //! screenwidth and height are defined in Video.h
{
	btnHome = NULL;
	for (int i = 0; i < 4; i++)
		pointer[i] = NULL;
	
	//! Initially, all input updates are allowed
	bGuiInputUpdate = true;
}

Application::~Application()
{
	RemoveAll();
	
	if(btnHome)
		delete btnHome;
	
	for (int i = 0; i < 4; i++)
		if(pointer[i]) delete pointer[i];

	//! remove still outstanding deletes without any dependencies
	for(u32 i = 0; i < deleteList.size(); i++)
		delete deleteList[i];
}

void Application::quit()
{
	FadeOut();

	exitApplication = true;
}

void Application::hide()
{
	RemoveAll();
}

void Application::show()
{
	Append(SearchWindow::Instance());
	Append(MusicPlayer::Instance());
	Append(ProgressWindow::Instance());
	
	//! Append MainMenu instance
	Append(MainMenu::Instance());
	MainMenu::Instance()->show();
}

void Application::FadeIn()
{
	for(int i = 255; i >= 0; i -= 15)
	{
		Draw();
		GXColor fadeoutColor = (GXColor){0, 0, 0, i};
		Menu_DrawRectangle(0, 0, 100.0f, screenwidth, screenheight, &fadeoutColor, false, true);
		Menu_Render();
		if(!IsVisible())
			SetVisible(true);
	}
}

void Application::FadeOut()
{
	for(int i = 0; i <= 255; i += 15)
	{
		Draw();
		GXColor fadeoutColor = (GXColor){0, 0, 0, i};
		Menu_DrawRectangle(0, 0, 100.0f, screenwidth, screenheight, &fadeoutColor, false, true);
		Menu_Render();
	}

	SetVisible(false);
}

void Application::exec()
{
	while(!exitApplication)
	{
		if(needReload)
		{
			needReload = false;
			ExecuteReload();
		}
		
		updateEvents();
	}

	ExitApp();
}

void Application::updateEvents()
{
	if(exitApplication || reloading)
		return;
	
	if(bShutdown)
		Sys_Shutdown();
	else if(bReset)
		Sys_Reboot();

	//! first update the inputs
	UpdatePads();
	for (int i = 0; i < 4 && (reloadType == RELOAD_NONE); i++)
		pointer[i]->Update(&userInput[i]);
	
	//! update the gui elements with new inputs
	if(bGuiInputUpdate)
	{
		for (int i = 0; i < 4; i++)
		{
			if(updateOnlyElement.empty())
				Update(&userInput[i]);
			else
				updateOnlyElement.back()->Update(&userInput[i]);
			
			//! always update the home menu, everywhere
			btnHome->Update(&userInput[i]);
		}
	}
	
	//! render everything
	Draw();

	//! render wii mote pointer always last and on top
	for (int i = 3; i >= 0 && (reloadType == RELOAD_NONE); i--)
		pointer[i]->Draw();

	Menu_Render();

	//! delete elements that were queued for delete after the rendering is done
	if(!deleteList.empty())
		ProcessDeleteQueue();
	
	//! ScreenSaver
	if(!screenSaver && Settings.Screensaver && ControlActivityTimeout())
	{
		screenSaver = new ScreenSaver();
		screenSaver->Closing.connect(this, &Application::OnScreenSaverClosing);
	}
	else if(screenSaver && screenSaver->NeedClose())
	{
		screenSaver->quit();
	}
	
	//! Check Update Request
	if(MainMenu::Instance()->updateRequested)
		MainMenu::Instance()->ExecuteUpdate();
	
	//! execute tasks that require main thread execution
	if(!postUpdateTasks.empty())
		ProcessPostUpdateTasks();
}

void Application::ResetPads()
{
	bGuiInputUpdate = false;
	updateEvents();
	updateEvents();
	bGuiInputUpdate = true;
}

void Application::Append(GuiElement *e)
{
	m_mutex.lock();
	GuiFrame::Append(e);
	m_mutex.unlock();
}

void Application::SetUpdateOnly(GuiElement *e)
{
	UnsetUpdateOnly(e);
	m_mutex.lock();
	updateOnlyElement.push_back(e);
	m_mutex.unlock();
}

void Application::UnsetUpdateOnly(GuiElement *e)
{
	for(u32 i = 0; i < updateOnlyElement.size(); ++i)
	{
		if(updateOnlyElement[i] == e)
		{
			m_mutex.lock();
			updateOnlyElement.erase(updateOnlyElement.begin()+i);
			m_mutex.unlock();
		}
	}
}

void Application::ProcessDeleteQueue(void)
{
	m_mutex.lock();
	for(u32 i = 0; i < deleteList.size(); ++i)
	{
		GuiElement *parent = deleteList[i]->GetParent();
		//! Only remove elements when they are currently not animated
		//! otherwise wait till animation is finished
		if(!parent || !deleteList[i]->IsAnimated())
		{
			//! since i am lazy and don't remove them usually, this is where i remove it
			if(parent)
			{
				GuiFrame *parentFrame = dynamic_cast<GuiFrame *>(parent);
				if(parentFrame)
					parentFrame->Remove(deleteList[i]);
			}
			
			UnsetUpdateOnly(deleteList[i]);
			delete deleteList[i];
			deleteList.erase(deleteList.begin() + i);
			i--;
		}
	}
	m_mutex.unlock();
}

void Application::PushForDelete(GuiElement *e)
{
	if(!e)
		return;

	//! elements that are queued for delete are only rendered and not updated with buttons
	e->SetState(STATE_DISABLED);

	m_mutex.lock();
	deleteList.push_back(e);
	m_mutex.unlock();
}

void Application::ProcessPostUpdateTasks(void)
{
	while(!postUpdateTasks.empty())
	{
		m_mutex.lock();
		ThreadedTask *task = postUpdateTasks.front();
		postUpdateTasks.pop();
		m_mutex.unlock();
		
		task->Execute();
		delete task;
	}
}

void Application::addPostRenderTask(ThreadedTask *t)
{
	m_mutex.lock();
	postUpdateTasks.push(t);
	m_mutex.unlock();
}

/***********************************************************
 * Main application initialization.
 *
 * Called only once at the beginning of the application.
 ***********************************************************/
void Application::init(void)
{
	//! Set main thread prio very high as it is the render thread
	LWP_SetThreadPriority(LWP_GetSelf(), 121);

	//! Setup WiiMote Pointers
	pointer[0] = new WiiPointer("player1_point.png");
	pointer[1] = new WiiPointer("player2_point.png");
	pointer[2] = new WiiPointer("player3_point.png");
	pointer[3] = new WiiPointer("player4_point.png");

	//! Setup the music player
	MusicPlayer::Instance()->SetVolume(Settings.MusicVolume);
	MusicPlayer::Instance()->SetLoop(Settings.BGMLoopMode);
	MusicPlayer::Instance()->SetAlignment(thAlign("right - music player align hor") | thAlign("top - music player align ver"));
	MusicPlayer::Instance()->SetPosition(thInt("30 - music player pos x"), thInt("230 - music player pos y"));
	
	//! Setup the devices state check
	DevicesSwap::Instance();

	//! setup the home menu button
	trigHome.SetButtonOnlyTrigger(-1, WiiControls.HomeButton | ClassicControls.HomeButton << 16, GCControls.HomeButton);
	
	btnHome = new GuiButton(0, 0);
	btnHome->SetTrigger(&trigHome);
	btnHome->Clicked.connect(this, &Application::OnHomeButtonClick);
	
	screenSaver = NULL;
}

void Application::OnHomeButtonClick(GuiButton *sender UNUSED, int pointer UNUSED, const POINT &p UNUSED)
{
	if(screenSaver || DevicesSwap::Instance()->IsWaiting())
		return;
	
	//! disable home menu button clicks while we are inside the home menu
	btnHome->SetClickable(false);
	
	HomeMenu *homeMenu = new HomeMenu();
	homeMenu->Closing.connect(this, &Application::OnHomeMenuClosing);
}

void Application::OnHomeMenuClosing(GuiFrame *menu UNUSED)
{
	btnHome->SetClickable(true);
}

void Application::OnScreenSaverClosing(GuiFrame *saver UNUSED)
{
	screenSaver = NULL;
}

void Application::ExecuteReload()
{
	for(int i = 0; i < 4; i++)
		pointer[i]->SetImage(fmt("player%i_point.png", i+1));

	hide();
	
	MainMenu::DestroyInstance();
	MusicPlayer::DestroyInstance();
	ProgressWindow::DestroyInstance();
	
	ProcessDeleteQueue();
	
	MusicPlayer::Instance()->SetVolume(Settings.MusicVolume);
	MusicPlayer::Instance()->SetLoop(Settings.BGMLoopMode);
	MusicPlayer::Instance()->SetAlignment(ALIGN_RIGHT | ALIGN_TOP);
	MusicPlayer::Instance()->SetPosition(30, 230);
	
	show();

	reloading = false;
	
	FadeIn();
	
	reloadType = RELOAD_NONE;
}
